require 'rubygems'
require 'metaid'

class XML
  # blank slate
  instance_methods.each { |m| undef_method m unless (m =~ /^__|instance_eval|meta/)}
  
  # Each item in @doc is an array containing three values: type, value, attributes
  
  def initialize(tag = nil, atts = nil, &block)
    @doc = ''
    @doc << _fmt_open(tag, atts) if tag
    instance_eval(&block)
    @doc << _fmt_close(tag) if tag
  end
  
  def method_missing(tag, *args, &block)
    meta_def(tag) do |*args|
      @__to_s = nil # invalidate to_s cache
      if block
        @doc << _fmt_open(tag, args.first)
        instance_eval(&block)
        @doc << _fmt_close(tag)
      else
        value, atts = args.pop, args.pop
        subtags, atts = atts, nil if atts.is_a?(Array)
        if subtags
          @doc << _fmt_open(tag, atts)
          subtags.each {|k| __send__(k, value[k])}
          @doc << _fmt_close(tag)
        else
          @doc << _fmt_open(tag, atts)
          @doc << _fmt_value(value)
          @doc << _fmt_close(tag)
        end
      end
    end
    __send__(tag, *args)
  end
  
  def instruct!(atts = nil)
    @doc.insert(0, _fmt_instruct(atts || {:version => "1.0", :encoding => "UTF-8"}))
  end

  def to_s
    @doc
  end
  
  alias_method :inspect, :to_s
  
  def _fmt_open(tag, atts)
    atts ? "<#{tag} #{_fmt_atts(atts)}>" : "<#{tag}>"
  end

  def _fmt_close(tag)
    "</#{tag}>"
  end

  def _fmt_value(value)
    value.to_s
  end

  def _fmt_instruct(arg)
    "<?xml #{_fmt_atts(arg)}?>"
  end

  def _fmt_atts(atts)
    atts.inject([]) {|m, i| m << "#{i[0]}=#{i[1].to_s.inspect}"}.join(' ')
  end
end

def t1(list)
  xml = XML.new do
    instruct!
      reality(:time => Time.now) do
      event do
        type 1
        path "/sharon"
        value "abcdefg"
      end
      list.each {|i| item i}
    end
  end
  xml.to_s
end

puts t1([1, 3, 4])

puts "**************"

x = 100000

list = (1..x).map {
  {:quality => rand(5), :value => rand(77865)}
}

t1 = Time.now
xml = XML.new do
  states do 
    list.each {|s| state [:quality, :value], s}
  end
end
s1 = xml.to_s
t3 = Time.now
e1 = t3 - t1
r1 = x / e1
puts "build: #{e1} (#{r1})"

puts "**************"

t1 = Time.now
xml = XML.new(:states) do
  list.each {|s| state [:quality, :value], s}
end
s2 = xml.to_s
t3 = Time.now
e1 = t3 - t1
r1 = x / e1
puts "build: #{e1} (#{r1})"

puts "bad result" unless s1 == s2


__END__

class EventsController < ServerSide::Controller::Base.mount('/events/:format')
  invoke_by_parameter :format, :default => :html
  
  def before_response
    @events = Events.select
  end
  
  def xml
    render_xml('text/xml+svg') do
      events @events.to_xml
    end
  end
end

xml = XML.new do
  instruct!
  reality(:time => Time.now) do
    event do
      type 1
      path "/sharon"
      value "abcdefg"
    end
    list.each {|i| item i}
  end
end
xml.to_s
